namespace ImapX
{
    using System;
    using System.Collections;
    using System.IO;
    using System.Net.Security;
    using System.Net.Sockets;
    using System.Security.Authentication;
    using System.Security.Cryptography.X509Certificates;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Threading;
    using System.IO.Compression;
    using System.Diagnostics;

    public class ImapBase
    {
        protected int _commandCount;
        protected string _imapHost = "localhost";
        protected int _imapPort = 0x92;
        protected TcpClient _imapServer;
        protected Stream _imapStream;
        protected SslStream _imapSslStream;
        internal bool _isConnected;
        internal bool _isLogged;
        internal string _selectedFolder;
        protected string _userLogin;
        protected string _userPassword;
        protected bool _useSSL;
        protected bool isDebug;
        protected bool _disableRetries = false;
        protected int _retries = 3;

        protected Stream stream;

        protected ImapReader streamReader;
        protected StreamWriter streamWriter;
        StreamReader compressReader;
        StreamWriter compressWriter;

        public void Capability()
        {
            ArrayList sResultArray = new ArrayList();
            string command = "CAPABILITY\r\n";
            SendAndReceive(command);
        }

        public bool Connect()
        {
            return this.Connect(this._imapHost, this._imapPort, this._useSSL);
        }

        public bool Connect(string sHost, int nPort, bool useSSL)
        {
            this._useSSL = useSSL;
            bool flag = true;
            this._commandCount = 0;
            
            _selectedFolder = null;

            try
            {
                this._imapServer = new TcpClient(sHost, nPort);
                _imapStream = _imapServer.GetStream();

                if (useSSL)
                {
                    this._imapSslStream = new SslStream(_imapStream, false, new RemoteCertificateValidationCallback(this.ValidateServerCertificate), null);
                    try
                    {
                        this._imapSslStream.AuthenticateAsClient(sHost);
                    }
                    catch (AuthenticationException)
                    {
                        flag = false;
                        this._imapServer.Close();
                    }
                    stream = _imapSslStream;
                }
                else
                {
                    stream = _imapStream;
                }

                streamReader = new ImapReader(stream, Encoding.ASCII);
                stream.ReadTimeout = 60000;
                streamWriter = new StreamWriter(stream, Encoding.ASCII);
                streamWriter.AutoFlush = true;

                // reader/writer for compressed data
                compressReader = new StreamReader(new DeflateStream(stream, CompressionMode.Decompress), Encoding.ASCII);
                compressWriter = new StreamWriter(new DeflateStream(stream, CompressionMode.Compress), Encoding.ASCII);
                compressWriter.AutoFlush = true;


                string str = ReadLine();
                if (str.StartsWith("* OK"))
                {
                    this.Capability();
                }
                else
                {
                    flag = false;
                }
            }
            catch
            {
                throw new ImapException("dont connect");
            }
            this._imapHost = sHost;
            this._imapPort = nPort;
            this._isConnected = true;
            return flag;
        }

        public bool Disconnect()
        {
            this._commandCount = 0;
            if (this._isConnected)
            {
                //if (this._imapSslStream != null)
                //{
                //    this._imapSslStream.Close();
                //}
                if (streamReader != null)
                {
                    streamReader.Close();
                }
                if (streamWriter != null)
                {
                    streamWriter.Close();
                }
                if (this._imapServer != null)
                {
                    this._imapServer.Close();
                }
            }
            this._isConnected = false;
            return true;
        }

        public void LogIn()
        {
            LogIn(this._userLogin, this._userPassword);
        }

        public void LogIn(string login, string password)
        {
            if (!this._isConnected)
            {
                throw new ImapException("Not Connection");
            }
            if (string.IsNullOrEmpty(login))
            {
                throw new ImapException("Login is null or empty");
            }
            if (string.IsNullOrEmpty(password))
            {
                throw new ImapException("Password is null or empty");
            }
            string command = "LOGIN \"" + login + "\" \"" + password + "\"\r\n";
            SendAndReceive(command);
            _isLogged = true;
            _userLogin = login;
            _userPassword = password;
        }

        public void LogOut()
        {
            if (this._isLogged)
            {
                ArrayList sResultArray = new ArrayList();
                string command = "LOGOUT\r\n";
                SendAndReceive(command);
                this._isLogged = false;
            }
        }

        protected void Reconnect()
        {
            string selectedFolder = _selectedFolder;

            try
            {
                LogOut();
            }
            catch { }
            try
            {
                Disconnect();
            }
            catch { }

            Connect();
            LogIn();
            if (selectedFolder != null)
            {
                SelectFolder(selectedFolder);
            }
        }

        public MessageCollection SearchUtf8Data(byte[] data)
        {
            byte[] buffer = data;
            try
            {
                streamWriter.BaseStream.Write(buffer, 0, data.Length);
                bool flag = true;
                MessageCollection messages = new MessageCollection();
                while (flag)
                {
                    string str = ReadLine();

                    foreach (string str2 in str.ToString().Split(new char[] { ' ' }))
                    {
                        int num;
                        if (int.TryParse(str2, out num))
                        {
                            Message item = new Message();
                            item.MessageUid = num;
                            messages.Add(item);
                        }
                    }
                    flag = false;
                }
                return messages;
            }
            catch (Exception)
            {
                return null;
            }
        }

        public string SendAndReceive(string command)
        {
            int tries = 1;

            if (!_disableRetries)
            {
                tries += _retries;
            }

            while (tries > 0)
            {
                try
                {
                    SendCommand(command);
                    return ReceiveResult();
                }
                catch (Exception ex)
                {
                    tries--;

                    if (tries == 0)
                    {
                        throw new ImapException("Error sending command: '" + command + "'", ex);
                    }

                    Debug.Assert(_disableRetries == false);
                    _disableRetries = true;

                    try
                    {
                        Reconnect();
                    }
                    finally
                    {
                        _disableRetries = false;
                    }
                }
            }

            Debug.Assert(false); // should never get here
            return "";
        }

        /*
        private string ReceiveResult()
        {
            var sResultArray = new ArrayList();

            ReceiveResult(ref sResultArray);

            return String.Join("\r\n", (string[])sResultArray.ToArray(typeof(string)));
        }
        */

        public string ReceiveResult(bool throwException = true)
        {
            StringBuilder sb = new StringBuilder();

            bool done = false;
            while (!done)
            {
                string str2 = ReadLine();
                sb.Append(str2 + "\r\n");

                if (str2 == null) continue;

                Match match = Regex.Match(str2, @"\*.*?FETCH.*?\{(?<dataLength>\d+)\}");

                if (match.Success)
                {
                    int dataLength = Convert.ToInt32(match.Groups["dataLength"].Value);
                    sb.Append(ReadData(dataLength));
                }
                else
                {
                    Match match2 = Regex.Match(str2, "IMAP00" + _commandCount + @"\s(?<code>\w+)(\s(?<message>.+))?$");

                    if (match2.Success)
                    {
                        string code = match2.Groups["code"].Value;
                        string message = match2.Groups["message"].Value;

                        switch (code)
                        {
                            case "OK":
                                done = true;
                                break;
                            case "NO":
                            case "BAD":
                                if (throwException)
                                {
                                    throw new ImapException(message);
                                }
                                done = true;
                                break;
                        }
                    }
                    if (str2.StartsWith("+ "))
                    {
                        done = true;
                    }
                }
            }
            return sb.ToString();
        }

        public void SendCommand(string command)
        {
            this._commandCount++;
            string str = string.Concat(new object[] { "IMAP00", this._commandCount, " ", command });

            Write(str);
        }

        // For appending message data
        public string SendData(string data)
        {
            Write(data);
            Write("\r\n");
            return ReceiveResult();
        }

        public string SendData(byte[] data)
        {
            stream.Write(data, 0, data.Length);
            Write("\r\n");
            return ReceiveResult();
        }

        private void Write(string data)
        {
            TraceWrite(data);

            streamWriter.Write(data);
        }

        public bool IsCompressionEnabled
        {
            get;
            private set;
        }

        public void EnableCompression()
        {
            if (IsCompressionEnabled) throw new Exception("Compression already enabled.");

            string result = SendAndReceive("COMPRESS DEFLATE\r\n");

            IsCompressionEnabled = true;
        }

        private string ReadData(int length)
        {
            byte[] buffer = new byte[length];
            int actualRead = 0;
            int lastRead = 0;
            string s;

            try
            {
                while (true)
                {
                    actualRead += streamReader.Read(buffer, actualRead, Math.Min(length - actualRead, 5000));
                    Trace("Read " + actualRead.ToString() + " of " + length.ToString());
                    if (actualRead == length) break;
                    if (actualRead == lastRead) throw new Exception("Timeout reading data");
                    lastRead = actualRead;
                    Thread.Sleep(1);
                }
            }
            finally
            {
                s = Encoding.ASCII.GetString(buffer);
                //TraceRead(s);
            }
            return s;
        }

        private void TraceRead(string s)
        {
            Trace("<<|" + s);
        }

        private void TraceWrite(string s)
        {
            Trace(">>|" + s);
        }

        public String TraceID { get; set; }

        private void Trace(string s)
        {
            if (isDebug)
                Console.WriteLine(((TraceID == null) ? "" : TraceID + "|") + s);
        }

        public string ReadLine()
        {
            string result = streamReader.ReadLine();
            if (result == null) throw new ImapException("Readline returned null");
            if (isDebug)
            {
                TraceRead(result + "\r\n");
            }
            return result;
        }

        private bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            if (sslPolicyErrors == SslPolicyErrors.None)
            {
                return true;
            }
            Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
            return false;
        }

        public string Host
        {
            get
            {
                return this._imapHost;
            }
            set
            {
                this._imapHost = value;
            }
        }

        public bool IsDebug
        {
            get
            {
                return this.isDebug;
            }
            set
            {
                this.isDebug = value;
            }
        }

        public int Port
        {
            get
            {
                return this._imapPort;
            }
            set
            {
                this._imapPort = value;
            }
        }

        public string UserLogin
        {
            get
            {
                return this._userLogin;
            }
            set
            {
                this._userLogin = value;
            }
        }

        public string UserPassword
        {
            get
            {
                return this._userPassword;
            }
            set
            {
                this._userPassword = value;
            }
        }

        public bool UseSsl
        {
            get
            {
                return this._useSSL;
            }
            set
            {
                this._useSSL = value;
            }
        }

        public void SelectFolder(string folderName)
        {
            if (string.IsNullOrEmpty(folderName))
            {
                return;
            }
            if (folderName != _selectedFolder)
            {
                ArrayList sResultArray = new ArrayList();
                string command = "SELECT \"" + folderName + "\"\r\n";
                SendAndReceive(command);
                _selectedFolder = folderName;
            }
        }


    }
}

